package com.share.common.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONPath;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.*;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.imageio.ImageIO;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * 请求方式可能是 json或者表单
 * 返回数据全部都是json
 * <p>
 * OkHttpClient client = new OkHttpClient.Builder()
 * client.connectTimeout(10, TimeUnit.SECONDS) //  链接建立的超时时间,一般是3秒
 * client.readTimeout(10, TimeUnit.SECONDS) // 读取超时---核心参数
 * <p>
 * HttpURLConnection--jdkzidai
 * HttpURLConnection httpconn =(HttpURLConnection) newURL(url).openConnection();
 * httpconn.setConnectTimeout(1000);//连接主机的超时时间  ---链接建立的超时时间,一般是3秒
 * httpconn.setReadTimeout(1000);//读取数据的超时时间---核心参数
 * <p>
 * httpclient
 * connectTimeOut：建立连接时间 三次握手  ---链接建立的超时时间,一般是3秒
 * socketTimeOut：链接建立成功后,数据包传输之间时间超时限制，数据包，非此次接口调用时间，一定要设置 否则长时间等待---核心参数
 * connectionRequestTimeOut：从连接池获取连接的超时限制，不常用,一般是3秒
 */
@Slf4j
public class HttpUtil {
    /**
     * 初始化配置httpclient连接池
     */
    private static final String CHARSET_UTF_8 = "UTF-8";
    private static final String CONTENT_TYPE_JOSN = "application/json";
    private static final String CONTENT_TYPE_FORM = "application/x-www-form-urlencoded";

    private static SSLContext ctx;
    private static PoolingHttpClientConnectionManager poolMgr;
    private static RequestConfig requestConfig;

    //http最大来接数
    public final static int MAX_TOTAL_CONNECTIONS = 800;

    //同一路由http最大连接数
    public final static int MAX_ROUTE_CONNECTIONS = 100;

    //从连接池获取可用连接的等待时间
    public final static int POOL_CONNECT_TIMEOUT = 3000;

    //与服务器建立连接的超时时间
    public final static int CONNECT_TIMEOUT = 3000;

    //读取超时时间
    public final static int READ_TIMEOUT = 3500;


    static {
        Registry<ConnectionSocketFactory> registry = null;
        try {
            //ssl连接的配置
            ctx = createSSLContext();

            //这里要关联http和https即可
            RegistryBuilder registryBuilder = RegistryBuilder.<ConnectionSocketFactory>create();
            registryBuilder.register("http", new PlainConnectionSocketFactory());
            registryBuilder.register("https", new SSLConnectionSocketFactory(ctx, NoopHostnameVerifier.INSTANCE));
            registry = registryBuilder.build();
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 设置连接池
        poolMgr = new PoolingHttpClientConnectionManager(registry);
        poolMgr.setMaxTotal(MAX_TOTAL_CONNECTIONS);     //http最大来接数
        poolMgr.setDefaultMaxPerRoute(MAX_ROUTE_CONNECTIONS);  //同一路由http最大连接数

        //无效连接的关闭---触发时机是下一次请求时
        poolMgr.closeExpiredConnections();// 关闭失效连接
        poolMgr.closeIdleConnections(3 * 60 * 1000, TimeUnit.MILLISECONDS);  //关闭空闲超过3分钟的连接

        // 设置requestConfig---初始化一个超时配置
        // HttpClient内部有三个超时时间设置：连接池获取可用连接超时，连接超时，读取数据超时
        RequestConfig.Builder configBuilder = RequestConfig.custom();
        configBuilder.setConnectionRequestTimeout(POOL_CONNECT_TIMEOUT); //从连接池获取可用连接的等待时间
        configBuilder.setConnectTimeout(CONNECT_TIMEOUT);//与服务器建立tcp连接的超时时间
        configBuilder.setSocketTimeout(READ_TIMEOUT); //http连接获取响应的超时时间
        requestConfig = configBuilder.build();
    }

    //ssl配置模式一---很通用
    private static SSLContext createSSLContext() throws Exception {
        SSLContext ctx = SSLContext.getInstance("TLS");
        ctx.init(null, new TrustManager[]{new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        }}, null);

        return ctx;
    }

    //执行http或者https请求---依赖requestConfig(超时配置)+sslsf(http或者https)+poolMgr(连接池)
    private static <T> T execute(HttpRequestBase httpRequest, Class<T> reClassType, Integer reTryNum) throws Exception {
        //超时配置
        RequestConfig config = httpRequest.getConfig();
        RequestConfig tmpConfig = ((config == null) ? requestConfig : config);

        HttpClientBuilder httpClientBuilder = HttpClients.custom().useSystemProperties();
        httpClientBuilder.setConnectionManager(poolMgr);//设置连接池
        httpClientBuilder.setDefaultRequestConfig(tmpConfig);//设置连接时间,内置的初始化时永不超时
        //设置重试次数
        if (reTryNum != null && reTryNum > 0) {
            httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(reTryNum, false));
        }
        CloseableHttpClient httpClient = httpClientBuilder.build();

        try {
            CloseableHttpResponse response = httpClient.execute(httpRequest);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == HttpStatus.SC_OK) {
                if (byte[].class.equals(reClassType)) {
                    //返回字节数字,内容是文件
                    return (T) EntityUtils.toByteArray(response.getEntity());
                } else {
                    //默认是json接口模式,非文件
                    return (T) EntityUtils.toString(response.getEntity(), CHARSET_UTF_8);
                }
            } else {
                httpRequest.abort();//直接终止,如果状态码返回了非200,即使用了连接池,也不会释放连接
                return null;
            }
        } catch (Exception e) {
            httpRequest.abort();//直接终止,如果状态码返回了非200,即使用了连接池,也不会释放连接
            throw e;
        }
    }

    /**
     * 标准的http请求
     *
     * @param httpRequest
     * @return
     * @throws Exception
     */
    private static String execute(HttpRequestBase httpRequest, Integer reTryNum) throws Exception {
        String execute = execute(httpRequest, String.class, reTryNum);
        return execute;
    }

    /**
     * http下载文件下载文件
     *
     * @param url
     * @param postJsonStr   post请求的需要参数时调用
     * @param getParam      get请求时参数 ------------ 默认get请求
     * @param headers
     * @param readTimeOutMs
     * @return
     * @throws Exception
     */
    private static byte[] downFile(String url, String postJsonStr, Map<String, Object> getParam, Map<String, String> headers, Integer readTimeOutMs, Integer reTryNum) throws Exception {
        HttpRequestBase httpBase = null;//可能是get 也可能是post
        if (StringUtils.isEmpty(postJsonStr)) {
            //传递的参数
            Map<String, Object> tmpMap = new HashMap<>();
            if (!CollectionUtils.isEmpty(getParam)) {
                tmpMap.putAll(getParam);
            }

            //url参数
            Map<String, String> urlMap = ParamUtil.kvToMap(url);
            if (!CollectionUtils.isEmpty(urlMap)) {
                tmpMap.putAll(urlMap);
            }

            String s = ParamUtil.mapToKv(tmpMap);
            if (!StringUtils.isEmpty(s)) {
                if (url.indexOf("?") > 0) {
                    url = url.split("\\?")[0] + "?" + s;
                } else {
                    url = url + "?" + s;
                }
            }

            //url被修改了
            httpBase = new HttpGet(url);
        } else {
            HttpPost httpPost = new HttpPost(url);
            StringEntity entity = new StringEntity(postJsonStr, CHARSET_UTF_8);// 解决中文乱码问题
            entity.setContentEncoding(CHARSET_UTF_8);
            entity.setContentType(CONTENT_TYPE_JOSN);
            httpPost.setEntity(entity);

            //复制给httpbase
            httpBase = httpPost;
        }

        //覆盖头部
        if (headers != null && !headers.isEmpty()) {
            headers.forEach(httpBase::addHeader);
        }

        //超时时间
        if (readTimeOutMs != null) {
            if (readTimeOutMs < 0) { //永不超时
                httpBase.setConfig(RequestConfig.DEFAULT);
            } else {
                //复制系统默认的
                RequestConfig.Builder tmpConfigBuilder = RequestConfig.copy(requestConfig);
                tmpConfigBuilder.setSocketTimeout(readTimeOutMs); //http连接获取响应的超时时间
                httpBase.setConfig(tmpConfigBuilder.build());
            }
        }

        byte[] execute = execute(httpBase, byte[].class, reTryNum);
        return execute;
    }

    public static byte[] downFile(String url, String postJsonStr) throws Exception {
        byte[] bytes = downFile(url, postJsonStr, null, null, null, null);
        return bytes;
    }

    //下载文件
    public static String downFile(String url) throws Exception {
        byte[] bytes = downFile(url, null, null, null, null, null);
        return new String(bytes);
    }

    //下载文件,并保存到文件到本地---指定文件全路径
    public static boolean downSaveFile(String url, String saveFileName) throws Exception {
        return downSaveFile(url, new File(saveFileName));
    }

    //下载文件,并保存到文件到本地
    public static boolean downSaveFile(String url, File saveFile) throws Exception {
        File parentFile = saveFile.getParentFile();
        if (!parentFile.exists()) {
            parentFile.mkdirs();
        }

        byte[] bytes = downFile(url, null, null, null, null, null);
        boolean b = FileUtil.writeByte(saveFile, bytes);
        bytes = null;//手动释放掉
        return b;
    }

    //下载图片文件
    public static BufferedImage downImage(String url) throws Exception {
        byte[] bytes = downFile(url, null, null, null, null, null);
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        BufferedImage read = ImageIO.read(bais);
        bais.close();
        return read;
    }

    public static String post(String url, String jsonStr) throws Exception {
        return post(url, jsonStr, null, null, null);
    }

    public static String post(String url, String jsonStr, Integer readTimeOutMs) throws Exception {
        return post(url, jsonStr, null, readTimeOutMs, null);
    }

    /**
     * josn请求
     *
     * @param url
     * @param jsonStr
     * @param headers
     * @return
     * @throws Exception
     */
    public static String post(String url, String jsonStr, Map<String, String> headers) throws Exception {
        return post(url, jsonStr, headers, null, null);
    }

    public static String post(String url, String jsonStr, Map<String, String> headers, Integer readTimeOutMs) throws Exception {
        return post(url, jsonStr, headers, readTimeOutMs, null);
    }

    //时间单位是 毫秒  1秒(s)=1000毫秒(ms)1秒(s)=1000000 微秒(μs)1(微秒)us==1/1000000=0.000001s(秒)
    public static String post(String url, String jsonStr, Map<String, String> headers, Integer readTimeOutMs, Integer reTryNum) throws Exception {
        HttpPost httpPost = new HttpPost(url);

        if (readTimeOutMs != null) {
            if (readTimeOutMs < 0) { //永不超时
                httpPost.setConfig(RequestConfig.DEFAULT);
            } else {
                //复制系统默认的
                RequestConfig.Builder tmpConfigBuilder = RequestConfig.copy(requestConfig);
                tmpConfigBuilder.setSocketTimeout(readTimeOutMs); //http连接获取响应的超时时间
                httpPost.setConfig(tmpConfigBuilder.build());
            }
        }

        StringEntity entity = new StringEntity(jsonStr, CHARSET_UTF_8);// 解决中文乱码问题
        entity.setContentEncoding(CHARSET_UTF_8);
        entity.setContentType(CONTENT_TYPE_JOSN);
        httpPost.setEntity(entity);

        //覆盖头部
        if (headers != null && !headers.isEmpty()) {
            headers.forEach(httpPost::addHeader);
        }

        return execute(httpPost, reTryNum);
    }

    public static String postForm(String url, Map<String, Object> params, Map<String, String> headers) throws Exception {
        return postFormBase(url, params, headers, null, null);
    }

    //表单请求 --- 不支持文件
    private static String postForm(String url, Map<String, Object> params, Map<String, String> headers, Integer readTimeOutMs, Integer reTryNum) throws Exception {
        return postFormBase(url, params, headers, readTimeOutMs, reTryNum);
    }

    public static String postForm(String url, MultipartEntityBuilder builder, Map<String, String> headers) throws Exception {
        return postFormBase(url, builder, headers, null, null);
    }

    /**
     * //表单请求 --- 支持文件
     * MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();
     * entityBuilder.addBinaryBody(key, is, ContentType.DEFAULT_BINARY, key + "_orgin");
     * entityBuilder.addPart(key,new StringBody(ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8));  注意编码格式---是utf-8默认不是ascil
     *
     * @param url
     * @param builder
     * @param headers
     * @param readTimeOutMs
     * @param reTryNum
     * @return
     * @throws Exception
     */
    private static String postForm(String url, MultipartEntityBuilder builder, Map<String, String> headers, Integer readTimeOutMs, Integer reTryNum) throws Exception {
        return postFormBase(url, builder, headers, readTimeOutMs, reTryNum);
    }

    /**
     * 表单请求 --- 支持文件
     *
     * @param url
     * @param params  参数为 MultipartEntityBuilder或者是 Map
     * @param headers
     * @return
     * @throws Exception
     */
    private static String postFormBase(String url, Object params, Map<String, String> headers, Integer readTimeOutMs, Integer reTryNum) throws Exception {
        HttpPost httpPost = new HttpPost(url);
        if (params != null) {
            if (params instanceof Map) {
                Map<String, Object> mapParam = (Map<String, Object>) params;
                String str = ParamUtil.mapToKv(mapParam);//参数组装
                StringEntity entity = new StringEntity(str, CHARSET_UTF_8);
                entity.setContentEncoding(CHARSET_UTF_8);
                entity.setContentType(CONTENT_TYPE_FORM);
                httpPost.setEntity(entity);
            } else if (params instanceof MultipartEntityBuilder) {
                //MultipartEntityBuilder 类型不用设置参数
                MultipartEntityBuilder multipartParam = (MultipartEntityBuilder) params;
                HttpEntity entity = multipartParam.build();
                httpPost.setEntity(entity);
            }
        }

        if (readTimeOutMs != null) {
            if (readTimeOutMs < 0) { //永不超时
                httpPost.setConfig(RequestConfig.DEFAULT);
            } else {
                //复制系统默认的
                RequestConfig.Builder tmpConfigBuilder = RequestConfig.copy(requestConfig);
                tmpConfigBuilder.setSocketTimeout(readTimeOutMs); //http连接获取响应的超时时间
                httpPost.setConfig(tmpConfigBuilder.build());
            }
        }

        //覆盖头部
        if (headers != null && !headers.isEmpty()) {
            headers.forEach(httpPost::addHeader);
        }

        return execute(httpPost, reTryNum);
    }

    public static String get(String url) throws Exception {
        return get(url, null, null, null, null);
    }

    public static String get(String url, Integer readTimeOutMs) throws Exception {
        return get(url, null, null, readTimeOutMs, null);
    }

    public static String get(String url, Map<String, Object> params) throws Exception {
        return get(url, params, null, null, null);
    }

    public static String get(String url, Map<String, Object> params, Integer readTimeOutMs) throws Exception {
        return get(url, params, null, readTimeOutMs, null);
    }

    public static String get(String url, Map<String, Object> params, Map<String, String> headers) throws Exception {
        return get(url, params, headers, null, null);
    }

    //时间单位是 毫秒  1秒(s)=1000毫秒(ms)1秒(s)=1000000 微秒(μs)1(微秒)us==1/1000000=0.000001s(秒)
    //get请求的headers中没有content-type这个字段，因为get请求不存在请求实体部分，键值对参数放置在 URL 尾部，因此请求头不需要设置 Content-Type 字段
    public static String get(String url, Map<String, Object> params, Map<String, String> headers, Integer readTimeOutMs, Integer reTryNum) throws Exception {
        //传递的参数
        Map<String, Object> tmpMap = new HashMap<>();
        if (!CollectionUtils.isEmpty(params)) {
            tmpMap.putAll(params);
        }

        //url参数
        Map<String, String> urlMap = ParamUtil.kvToMap(url);
        if (!CollectionUtils.isEmpty(urlMap)) {
            tmpMap.putAll(urlMap);
        }

        String s = ParamUtil.mapToKv(tmpMap);
        if (!StringUtils.isEmpty(s)) {
            if (url.indexOf("?") > 0) {
                url = url.split("\\?")[0] + "?" + s;
            } else {
                url = url + "?" + s;
            }
        }

        //传入的url必须是全部的url参数,否则有问题
        HttpGet httpGet = new HttpGet(url);

        //覆盖头部
        if (headers != null && !headers.isEmpty()) {
            headers.forEach(httpGet::addHeader);
        }

        //超时时间
        if (readTimeOutMs != null) {
            if (readTimeOutMs < 0) { //永不超时
                httpGet.setConfig(RequestConfig.DEFAULT);
            } else {
                //复制系统默认的
                RequestConfig.Builder tmpConfigBuilder = RequestConfig.copy(requestConfig);
                tmpConfigBuilder.setSocketTimeout(readTimeOutMs); //http连接获取响应的超时时间
                httpGet.setConfig(tmpConfigBuilder.build());
            }
        }
        return execute(httpGet, reTryNum);
    }

    public static String delete(String url) throws Exception {
        HttpDelete httpDelete = new HttpDelete(url);
        return execute(httpDelete, null);
    }

    public static InputStream getInputStream(String url) throws Exception {
        return getInputStream(new URL(url));
    }

    public static InputStream getInputStream(URL urlPath) throws Exception {
        InputStream is = null;
        String protocol = urlPath.getProtocol();
        if ("https".equals(protocol.toLowerCase())) {
            HttpsURLConnection https = (HttpsURLConnection) urlPath.openConnection();
            https.setSSLSocketFactory(ctx.getSocketFactory());
            https.setHostnameVerifier(NoopHostnameVerifier.INSTANCE);
            is = https.getInputStream();
        } else {
            is = urlPath.openStream();
        }
        return is;
    }

    /**
     * http请求翻页处理所有
     *
     * @param totalPageNumPath 总页数字段的jsonpath位置
     * @param f1_http_request  封装http请求
     * @param f2_result_merge  http请求结果的合并
     * @param executor         线程池
     *
     * Function<Integer, String> f1_http_request   泛型 第一个参数是 分页参数，第二个是返回值
     * Function<List<String>, String> f2_result_merge  泛型 第一个参数是 f1所有返回结果的集合，第二个参数是f2处理的结果
     *
     * @return
     */
    public static String pageAll(String totalPageNumPath, Function<Integer, String> f1_http_request, Function<List<String>, String> f2_result_merge, Executor executor) {
        String result = null;
        List<String> reList = new LinkedList<>();
        try {
            //创建任务
            int totalPageNum = 1;
            List<CompletableFuture<String>> futureList = new ArrayList<>();
            for (int i = 1; i <= totalPageNum; i++) {
                int pageNo = i;
                if (i == 1) {
                    String res1 = f1_http_request.apply(pageNo);
                    totalPageNum = (Integer) JSONPath.read(res1, totalPageNumPath);
                } else {
                    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> f1_http_request.apply(pageNo), executor);
                    futureList.add(future);
                }
            }

            CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).get(10, TimeUnit.SECONDS);
            for (CompletableFuture<String> future : futureList) {
                //提取每次的调用结果集,如果失败直接跳过
                String resTmp = future.get(5, TimeUnit.SECONDS);
                reList.add(resTmp);
            }

            result = f2_result_merge.apply(reList);
        } catch (Exception e) {
            log.error("翻页并发请求异常 reList={}", JSON.toJSONString(reList), e);
        }
        return result;
    }

    public static void main(String[] args) {
        try {
            String url = "http://beta.api.snyq.ltd:8245/beta/upload";
            Map<String, Object> map = new HashMap<>();
            map.put("name", "1");
//            map.put("file1", new File("C:\\Users\\Administrator\\requirements.txt"));
//            map.put("file2", new File("C:\\Users\\Administrator\\.cnpmrc"));

            map.put("file1", new URL("http://192.168.88.55/commons-lang-2.6.jar"));
            map.put("file2", new URL("http://192.168.88.55/commons-lang-2.6.jar"));

//            byte [] aa = HttpUtil.downFile("http://192.168.88.55/commons-lang-2.6.jar",null);
//            byte [] aa1 = HttpUtil.downFile("http://192.168.88.55/commons-lang-2.6.jar",null);

//            map.put("file1", aa);
//            map.put("file2", aa1);

            String s = postForm(url, map, null, 10000, null);
            System.out.println(s);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
